
using System;
using System.IO;
using System.Threading;
using System.Windows.Media;

internal class NES{
	
	internal UI gui;
	internal CPU cpu;
	internal PPU ppu;
	internal PAPU papu;
	internal Memory cpuMem;
	internal Memory ppuMem;
	internal Memory sprMem;
	internal MemoryMapper memMapper;
	internal PaletteTable palTable;
	internal ROM rom;
	internal GameGenie gameGenie;
	int cc;
	
	internal Stream romFile;
	bool _isRunning = false;
    internal ByteBuffer saveState;
    internal AutoResetEvent readyEvt = new AutoResetEvent(false);

    internal virtual int Framerate
    {
        set
        {
            Globals.preferredFrameRate = value;
            Globals.frameTime = 0xf4240 / value;
            this.papu.setSampleRate(this.papu.getSampleRate(), false);
        }
    }

    internal bool RomValid
    {
        get { return rom.valid; }
    }

    internal bool IsCPU   
    {
        get { return cpu!=null; }
    }

    // Creates the NES system.
	internal NES(UI gui){
		
		Globals.nes = this;
		this.gui = gui;
		
		// Create memory:
		cpuMem = new Memory(this,0x10000);	// Main memory (internal to CPU)
		ppuMem = new Memory(this,0x8000);	// VRAM memory (internal to PPU)
		sprMem = new Memory(this,0x100);	// Sprite RAM  (internal to PPU)
		
		
		// Create system units:
		cpu = new CPU(this);
		palTable = new PaletteTable();
		ppu = new PPU(this);
		papu = new PAPU(this);
		gameGenie = new GameGenie();
		
		// Init sound registers:
		for(int i=0;i<0x14;i++){
			if(i==0x10){
				papu.writeReg(0x4010,(short)0x10);
			}else{
				papu.writeReg(0x4000+i,(short)0);
			}
		}
		
		// Load NTSC palette:
		if(!palTable.loadNTSCPalette()){
			//System.Console.WriteLine("Unable to load palette file. Using default.");
			palTable.loadDefaultPalette();
		}
		
		// Initialize units:
		cpu.init();
		ppu.init();
		
		// Enable sound:
		enableSound(true);
		
		// Clear CPU memory:
		clearCPUMemory();
		
	}

    internal virtual void stateLoad()
    {
        if (this.cpu.Emulating)
        {
            this.cpu.loading = true;
        }
        else
        {

            if (this.saveState != null)
            {
                this.saveState.goTo(0);
                if (this.saveState.readByte() == 1)
                {
                    this.cpuMem.stateLoad(this.saveState);
                    this.ppuMem.stateLoad(this.saveState);
                    this.sprMem.stateLoad(this.saveState);
                    this.cpu.stateLoad(this.saveState);
                    this.memMapper.stateLoad(this.saveState);
                    this.ppu.stateLoad(this.saveState);
                }
            }
        }
    }

    internal virtual void stateSave()
    {
        if (this.cpu.Emulating)
        {
            this.cpu.saving = true;
        }
        else
        {
            this.saveState = new ByteBuffer(1, 0);
            this.saveState.putByte(1);
            this.cpuMem.stateSave(this.saveState);//ok
            this.ppuMem.stateSave(this.saveState);//ok
            this.sprMem.stateSave(this.saveState);//ok
            this.cpu.stateSave(this.saveState);//ok
            this.memMapper.stateSave(this.saveState);//ok
            this.ppu.stateSave(this.saveState);

            
        }
    }

    internal bool isRunning(){
		
		return _isRunning;
		
	}

    internal void startEmulation(){

        if (Globals.enableSound && !this.papu.isRunning())
        {
            this.papu.start();
        }
        if ((this.rom != null) && this.rom.Valid)
        {
            _isRunning = true;
        }
	}

    internal void stopEmulation(){
        this._isRunning = false;
	}
	
	internal void reloadRom(){
		
		if(romFile != null){
			loadRom(romFile);
		}
		
	}
	
	internal void clearCPUMemory(){
		
		short flushval = Globals.memoryFlushValue;
		for(int i=0;i<0x2000;i++){
			cpuMem.mem[i] = flushval;
		}
		for(int p=0;p<4;p++){
			int i = p*0x800;
			cpuMem.mem[i+0x008] = 0xF7;
			cpuMem.mem[i+0x009] = 0xEF;
			cpuMem.mem[i+0x00A] = 0xDF;
			cpuMem.mem[i+0x00F] = 0xBF;
		}
		
	}
	
	internal void setGameGenieState(bool enable){
		if(memMapper!=null){
			memMapper.setGameGenieState(enable);
		}
	}
	
	// Returns CPU object.
	internal CPU getCpu(){
		return cpu;
	}
	
	
	// Returns PPU object.
	internal PPU getPpu(){
		return ppu;
	}
	
	
	// Returns pAPU object.
	internal PAPU getPapu(){
		return papu;
	}
	
	
	// Returns CPU Memory.
	internal Memory getCpuMemory(){
		return cpuMem;
	}
	
	
	// Returns PPU Memory.
	internal Memory getPpuMemory(){
		return ppuMem;
	}
	
	
	// Returns Sprite Memory.
	internal Memory getSprMemory(){
		return sprMem;
	}
	
	
	// Returns the currently loaded ROM.
	internal ROM getRom(){
		return rom;
	}
	
	
	// Returns the GUI.
	internal UI getGui(){
		return gui;
	}
	
	
	// Returns the memory mapper.
	internal MemoryMapper getMemoryMapper(){
		return memMapper;
	}
	
	// Returns the Game Genie:
	internal GameGenie getGameGenie(){
		return gameGenie;
	}
	
	
	// Loads a ROM file into the CPU and PPU.
	// The ROM file is validated first.
    internal bool loadRom(Stream file){
		
		// Can't load ROM while still running.
		if(_isRunning){
			stopEmulation();
		}
		
		{
			// Load ROM file:
			
		rom = new ROM(this);
		rom.load(file);
		if(rom.isValid()){
				
				// The CPU will load
				// the ROM into the CPU
				// and PPU memory.
				
			reset();
				
			memMapper = rom.createMapper();
			memMapper.init(this);
			cpu.setMapper(memMapper);
			memMapper.loadROM(rom);
			ppu.setMirroring(rom.getMirroringType());
			
			if(gameGenie.getCodeCount()>0){
				memMapper.setGameGenieState(true);
				}
				
				this.romFile = file;
				
			}
			return rom.isValid();
		}
		
	}
	
	// Resets the system.
    internal void reset(){
		
        if(rom!=null){
            rom.closeRom();
        }

		if(memMapper != null){
			memMapper.reset();
		}
		
		cpuMem.reset();
		ppuMem.reset();
		sprMem.reset();
		
		clearCPUMemory();
		
		cpu.reset();
		cpu.init();
		ppu.reset();
		palTable.reset();	
		papu.reset();
		
		InputHandler joy1 = gui.getJoy1();
		if(joy1!=null){
			joy1.reset();
		}
		
	}
	
	
	// Enable or disable sound playback.
    internal void enableSound(bool enable){
		
		bool wasRunning = isRunning();
		if(wasRunning){
			stopEmulation();
		}
		
		if(enable){
			papu.start();
		}else{
			papu.stop();
		}
		
		//System.Console.WriteLine("** SOUND ENABLE = "+enable+" **");
		Globals.enableSound = enable;
		
		if(wasRunning){
			startEmulation();
		}
		
	}
	
	internal void setFramerate(int rate){
		
		Globals.preferredFrameRate = rate;
		Globals.frameTime = 1000000/rate;
		papu.setSampleRate(papu.getSampleRate(),false);
		
	}
	
	internal void destroy(){
		
		if(cpu!=null)cpu.destroy();
		if(ppu!=null)ppu.destroy();
		if(papu!=null)papu.destroy();
		if(cpuMem!=null)cpuMem.destroy();
		if(ppuMem!=null)ppuMem.destroy();
		if(sprMem!=null)sprMem.destroy();
		if(memMapper!=null)memMapper.destroy();
		if(rom!=null)rom.destroy();
		
		gui = null;
		cpu = null;
		ppu = null;
		papu = null;
		cpuMem = null;
		ppuMem = null;
		sprMem = null;
		memMapper = null;
		rom = null;
		gameGenie = null;
		palTable = null;
		
	}

    internal MediaStreamSource GetAudio()
    {
        return papu.line;
    }

    internal void WriteSound()
    {
        papu.line.WriteSound();
    }

    internal void Run()
    {
        getCpu().run();
    }

    internal void Emu()
    {
        cpu.emulate();
    }

    internal ImageSource GetImage()
    {
        return gui.ScreenView.Image;
    }

    internal void InvalidateImage()
    {
        gui.ScreenView.Image.Invalidate();
    }
}